/******************************************************************************
*
* This file is part of FreeTrack (http://code.google.com/p/qtfreetrack).
* BY Willy Gardiol (willy@gardiol.org)
*
* FreeTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FreeTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Foobar.  If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
#include "layertrack.h"
#include "layertrackconfig.h"
#include "layertracklabel.h"
#include "configwidget.h"
#include "configfile.h"
#include "trackwaypoint.h"
#include "filenavigator.h"
#include "workerloadgpx.h"
#include "workersavegpx.h"
#include "workerautoload.h"

#define N_COLORS 8
QColor colors[] = { QColor( 0,255,0 ), // 2 -green
                    QColor( 255,0,0 ), // 3 -red
                    QColor( 0,0,255 ), // 4 -blue
                    QColor( 128,128,128 ), // 5 -gray
                    QColor( 0,255,255), // 6 -cyan
                    QColor( 255,0,255), // 7 - yellow
                    QColor( 255,255,0), // 8 - purple
                    QColor( 255,255,255 ) // 1 -white
                           };
int cur_color = 0;

#define TRACK_DEF_TITLE "live stream"
#define TRACK_DEF_DESCRIPTION "set a description"

extern ConfigWidget* conf;
LayerTrack::LayerTrack(bool live):
        Layer( tr(TRACK_DEF_TITLE), tr(TRACK_DEF_DESCRIPTION)),
        autosave(false),
        tracking(false)
{
    is_live_track = live;

    size = TRACK_MIN_SIZE;
    color = colors[cur_color];

    emptyTrack();
    quickFilename = filename = "";
    extra_modified = gpx_modified = modified;
    autosave = conf->getAutosave();

    if ( is_live_track )
    {
        // Connect configuration variables status
        connect(conf, SIGNAL(autosaveChange(bool)),
                this, SLOT(setAutosave(bool)) );
    }
}

LayerTrack::~LayerTrack()
{
    if ( modified )
        saveTrack();
}

void LayerTrack::prepareToQuit()
{
    if ( is_live_track )
        processQuickSave();
}

void LayerTrack::addWaypoint( const TrackPoint& pt, const QString& name, const QString& description)
{
    if ( !is_live_track )
        return;

    TrackWaypoint wp( pt, name, description );
    appendWaypoint( wp );
    setModified( true, extra_modified );
    if ( autosave )
        quickSave( NULL, &wp );
}

void LayerTrack::appendWaypoint(TrackWaypoint& wp )
{
    waypoints.insert( wp.getId(), wp);
    expandBoundary( wp );
}

bool LayerTrack::removeWaypoint( int wid )
{
    if ( !is_live_track )
        return false;

    if ( !waypoints.contains(wid) )
         return false;
    waypoints.take(wid);
    setModified( true, extra_modified );
    return true;
}

TrackWaypoint LayerTrack::getWaypoint(int wid, QString& n, QString& d)
{
    if ( !waypoints.contains(wid) )
         return NULL;
    TrackWaypoint wp = waypoints.value(wid);
    n = wp.getName();
    d = wp.getDescription();
    return wp;
}

bool LayerTrack::updateWaypoint(int wid, const QString n, const QString d)
{
    if ( !is_live_track )
        return false;

    if ( !waypoints.contains(wid) )
         return false;
    TrackWaypoint wp = waypoints.value(wid);
    if ( wp.setName( n ) ||
         wp.setDescription( d ) )
    {
        waypoints.insert( wid, wp );
        setModified( true, extra_modified );
        return true;
    }
    return false;
}

QList<int> LayerTrack::listWaypointIDs()
{
    return waypoints.keys();
}

void LayerTrack::setModified(bool gpx, bool extra)
{
    // non live tracks cannot modify the GPX data
    if ( !is_live_track )
        gpx = false;

    modified = gpx || extra;
    gpx_modified = gpx;
    extra_modified = extra;
}

FreeTrackWidget* LayerTrack::getConfigureLayer()
{
    return new LayerTrackConfig(0,this);
}

FreeTrackWidget* LayerTrack::getLayerLabel()
{
    LayerTrackLabel* label = new LayerTrackLabel(color, size);
    connect( this, SIGNAL(colorChanged(QColor)),
             label, SLOT(colorUpdated(QColor)));
    connect( this, SIGNAL(sizeChanged(int)),
             label, SLOT(sizeUpdated(int)));
    return label;
}

QColor LayerTrack::getColor()
{
    return color;
}

void LayerTrack::setColor(QColor c)
{
    if ( color != c )
    {
        setModified( gpx_modified, true );
        color = c;
        emit colorChanged(c);
    }
}

int LayerTrack::getSize()
{
    return size;
}

void LayerTrack::setSize(int s)
{
    if ( (size != s) && (size >= TRACK_MIN_SIZE) && (size <= TRACK_MAX_SIZE) )
    {
        size = s;
        setModified( gpx_modified, true );
        emit sizeChanged(s);
    }
}

void LayerTrack::emptyTrack()
{
    data.clear();
    waypoints.clear();
    points.clear();
    bound_rect = QRectF();
    bound_rect_points = 0;
    last_length = length = 0.0;
    title = tr(TRACK_DEF_TITLE);
    description = tr(TRACK_DEF_DESCRIPTION);
    size = 3;
    if ( cur_color > N_COLORS )
        cur_color = 0;
    color = colors[cur_color++];
}

void LayerTrack::resetTrack()
{
    if ( !is_live_track )
        return;
    tracking = false;
    quickFilename = filename = "";
    emptyTrack();
    setModified( false, false );
    emit titleUpdated( title );
    emit descriptionUpdated( description );
    emit trackChanged( 0, 0.0 );
}

void LayerTrack::paintLayer(QPainter&painter, const QTransform& matrix, const QRectF& clip)
{
    // Skip, if clip does not intersects the layer boundary area.
    // bound_rect_points contains the number of waypoints+track points loaded. It might not be
    // equal to the sum of them, because some trackpoints mught have been removed. Anyway, for this
    // purpose this is the best option we have to use.

    // No points at all... return
    if ( bound_rect_points == 0 )
        return;
    // Two or more points, check if the boundary rect intersects the clip rect
    else if ( (bound_rect_points > 1) && !clip.intersects( bound_rect ) )
        return;
    // Otherwise, we have only one point and we can proceed

    // If there is at least one point to draw...
    if ( data.size() > 0 )
    {
        painter.save();
        painter.setPen( QPen( QBrush( color ), size, Qt::SolidLine  ) );
        // Draw the starting point
        // Why not use a small arrow bitmap here?
        QPoint startPoint = data.first().draw( painter, color, matrix );
        // Draw the path on the screen
        foreach (QPointF pt, points)
        {
            QPoint screenPt = (pt * matrix).toPoint();
            painter.drawLine( startPoint, screenPt );
            startPoint = screenPt;
        }
        // Draw the end point
        // Why not use a small arrow bitmap here too?
        data.last().draw( painter, color, matrix );
        painter.restore();
    }
    // Draw all waypoints!
    if ( conf->getDrawWaypoint() )
        foreach( TrackWaypoint wp, waypoints )
            wp.draw( painter, color, matrix );
    return;
}

bool LayerTrack::toXml(QByteArray*xml )
{
    QXmlStreamWriter stream(xml);
    WorkerSaveGpx runner( &stream,
                          &data,
                          &waypoints,
                          title,
                          description );
    return runner.run( tr("Saving GPX...") );
}

TrackPoint LayerTrack::getLastPoint()
{
    if ( data.length() > 0 )
        return data.last();
    else
        return TrackPoint();
}

void LayerTrack::expandBoundary(const QPointF& pt )
{
    // Before we can actually start expanding the boundary rect, we must at least have two points.
    // So we keep track of the number of points added into the boundary rect.

    // After we have at least two points so we can "grow" the boundary rect
    if ( bound_rect_points > 1 )
    {
        if ( pt.x() < bound_rect.left() )
            bound_rect.setLeft( pt.x() );
        if ( pt.y() < bound_rect.top() )
            bound_rect.setTop( pt.y() );
        if ( pt.x() > bound_rect.right() )
            bound_rect.setRight( pt.x() );
        if ( pt.y() > bound_rect.bottom() )
            bound_rect.setBottom( pt.y() );
    }
    // We arelady have one point, so set the bottom right corner
    else if ( bound_rect_points == 1 )
    {
        bound_rect.setBottomRight(pt);
        // Ensure this rect is valid (width & height > 0)
        if ( !bound_rect.isValid() )
            bound_rect = bound_rect.normalized();
    }
    // We have no points, so set the top left point.
    else if ( bound_rect_points == 0 )
    {
        bound_rect.setTopLeft(pt);
    }
    bound_rect_points++;
}

void LayerTrack::appendTrackPoint(const TrackPoint& new_point )
{   // Get the QPointF here, for speed reasons
    QPointF pt = new_point;

    if ( data.size() == 0 )
    {   // No points saved yet... initialize length to 0
        last_length = 0.0;
    }
    else
    {   // We have points, so add a point to the points list
        points.append( pt );
        // and calculate last length. Use pt which saves one function call in TrackPoint class
        last_length = data.last().distanceFrom( pt );
    }
    // update length
    length += last_length;
    // append the point
    data.append(new_point);
    // set the new boundaries
    expandBoundary(pt);
}

bool LayerTrack::loadGpx()
{
    QFile input(filename);
    if (!input.open(QIODevice::ReadOnly | QIODevice::Text))
        return false;

    QXmlStreamReader stream(&input);
    WorkerLoadGpx runner( &stream, this );
    if ( runner.run( tr("Loading GPX...") ) )
    {
        runner.getContents( title, description );
        emit titleUpdated( title );
        emit descriptionUpdated( description );
        emit trackChanged( data.count(), getTrackLength() );
        setModified( false, extra_modified );
        return true;
    }
    return false;
}

QString LayerTrack::getFilename()
{
    return filename;
}

QString LayerTrack::getExtraFilename()
{
    return QString("%1.txt").arg(filename);
}

bool LayerTrack::isFilenameSet()
{
    return filename != "";
}

void LayerTrack::processQuickSave()
{
    if ( quickFilename == "" )
        return;

    // If quick autosave and filename both exist then we must do something...
    if ( QFile::exists( quickFilename ) && QFile::exists( filename ) )
    {
        // We must load the quick autosave
        QFile quickFile(quickFilename);
        if (!quickFile.open(QIODevice::ReadOnly | QIODevice::Text))
            return;

        // Empty tracks, lines and waypoints
        emptyTrack();

        // TODO: put proper text and translation on this worker...
        QTextStream in(&quickFile);
        WorkerAutoLoad wal( &in, this );
        if ( wal.run("") )
        {
            emit trackChanged( data.count(), getTrackLength() );
            // and update the GPX with this data
            saveGpx();
            // Last, we delete the quick filename since we do not need it anymore.
            quickFile.close();
            quickFile.remove();
        }
    }
}

void LayerTrack::saveTrack( bool save_as )
{
    // NOTE: we must NEVER popup the file dialog box if save_as is FALSE!

    // first, save/convert any existing quick autosaves
    processQuickSave();

    // Store old filename, to force save to the new filename...
    QString old_filename = filename;
    // if we are asked explicitly to "save as"...
    if ( save_as )
    {   // Get new filename...
        if ( (filename = FileNavigator::saveTrack( tr("Save live track..."),
                                                  filename )) != "" )
        {   // Force save data to new file
            setModified(true,true);
            // Build "quick" filename for autosave:
            quickFilename = QString("%1.auto").arg(filename);
        }
        else
            quickFilename = "";
    }
    // Quit if filename is not set (or user "cancelled" the file dialog box)
    if ( filename != "" )
    {
        // If GPX has been modified or we are saving to a new filename...
        // note: on non-livetracks, gpx_modified is always FALSE.
        if ( gpx_modified || (old_filename != filename) )
            saveGpx();
        // if extra needs saving
        if ( extra_modified )
            saveExtra();
    }
}

bool LayerTrack::saveExtra()
{
    ConfigFile config( getExtraFilename(), QIODevice::WriteOnly );

    config.writeRow("title", title );
    config.writeRow("description", description );
    config.writeRow("size", QString("%1").arg(size) );
    if ( size > TRACK_MAX_SIZE )
        size = TRACK_MAX_SIZE;
    if ( size < TRACK_MIN_SIZE )
        size = TRACK_MIN_SIZE;
    config.writeRow("color", QString("rgb(%1,%2,%3)").arg( color.red() )
                                             .arg( color.green())
                                             .arg( color.blue()) );
    setModified( gpx_modified, false );
    return true;
}

bool LayerTrack::saveGpx()
{
    QFile output(filename);
    if (!output.open(QIODevice::WriteOnly | QIODevice::Text))
        return false;

    QByteArray xml;
    if ( toXml( &xml ) )
    {
        output.write( xml.constData(), xml.size() );
        setModified( false, extra_modified );
        return true;
    }
    return false;
}

bool LayerTrack::loadTrack(QString file)
{
    filename = file;
    if ( filename == "" )
        return false;

    // Build "quick" filename for autosave:
    quickFilename = QString("%1.auto").arg(filename);

    // there was an if ( !... ) return false; here but i removed it so that
    // when the GPX gets truncated to 0 bytes it will still load the .auto file.
    loadGpx();

    // Check if we have some pending quick autosave to restore (like after a crash)
    processQuickSave();

    // Extra data might be missing, but.. load is successful anyway.
    loadExtra();
    setModified( false, false );
    return true;
}

bool LayerTrack::loadExtra()
{
    ConfigFile config( getExtraFilename(), QIODevice::ReadOnly );
    if ( !config.getStatus() )
        return false;

    // Expression to parse the size ( ... = 3 )
    QRegExp rx_size("\\s*(\\d+)\\s*$");
    rx_size.setMinimal(true);
    // Expression to read the color ( ... = rgb( r, g, b ) )
    QRegExp rx_color("\\s*[rR][gG][bB]\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)\\s*$");
    rx_color.setMinimal(true);

    QString tmp;
    if ( (tmp = config.readRow("title")) != "" )
    {
        setTitle( tmp );
    }
    if ( (tmp = config.readRow("description")) != "" )
    {
        setDescription( tmp );
    }

    if ( config.readRow( "size", rx_size ))
    {
        setSize( ((QString)rx_size.cap(1)).toInt() );
    }
    if ( config.readRow("color", rx_color ))
    {
        setColor( QColor( ((QString)rx_color.cap(1)).toInt(),
                        ((QString)rx_color.cap(2)).toInt(),
                        ((QString)rx_color.cap(3)).toInt() ));
    }
    setModified( gpx_modified, false );
    return true;
}

void LayerTrack::setAutosave(bool as)
{
    if ( !is_live_track )
        return;

    autosave = as;
    if ( autosave )
        quickSave(NULL,NULL);
}

void LayerTrack::startTracking(bool st )
{
    if ( !is_live_track )
        return;

    tracking = st;
}

int LayerTrack::countTrackPoints()
{
    return data.count();
}

qreal LayerTrack::getTrackLength()
{   // in km
    return length/1000.0;
}

void LayerTrack::removeTrackPoint()
{   // Removing the last track point is a bit complicated...

    // do not do anything if we do not have at least one point...
    // AND we have a valid last_length, which means we can backtrack.
    if ( (data.size() > 0) && (last_length > 0.0) )
    {
        // 1) remove the last point from the list
        data.takeLast();
        // 2) remove the last points point
        points.takeLast();
        // 3) reduce the track length of the removed leg length
        length -= last_length;
        // 4) reset last_length, because now we cannot "backtrack" anymore until a new point is added
        last_length = 0.0;
    }
}

void LayerTrack::addPoint(const TrackPoint& position)
{
    // Only livetracks...
    if ( !is_live_track )
        return;

    // Ignore this point if we are not tracking
    if ( !tracking )
        return;

    // Ignore points with more than 100mt of precision... They clearly come from some
    // a-gps or network-based method, but they are quite useless for a real track.
    if ( position.getPrecision() > 100 )
        return;

    // We do not append points which have speed > 300 m/sec (about 1080km/h)
    if ( position.getSpeed() < 300 )
    {
        // If we already have at least one point, we must check is this new point can be added or not...
        if ( !data.isEmpty() )
        {
            // Store last point, if any
            TrackPoint myLast = data.last();
            // we do not append points overlapping the last one (assume spurious gps)
            // except in the case of a more precise point, wihch will override the last less precise point.
            if ( myLast.isOverlapping( position ) )
            {   // Remember: precision is WORSE when its bigger (Actually, its in meters!!!)
                if ( myLast.getPrecision() <= position.getPrecision() )
                    return; // So we ignore the new point!
                // This point is more precise, so we remove the last point...
                removeTrackPoint();
            }
        }
        // Now append the new valid point!
        appendTrackPoint( position );
        setModified( true, extra_modified );
        // Check if we must autosave the track
        if ( autosave )
            quickSave( &position, NULL ); // do not use position here!
        // inform the world we have a new point!!!
        emit newPointAdded();
        emit trackChanged( data.size(), getTrackLength());
    }
}

void LayerTrack::quickSave(const TrackPoint* tp, const TrackWaypoint* wp)
{
    if ( quickFilename == "" )
        return;

    // If quick auto save is not present, do a preliminary save of everything
    if ( !QFile::exists( quickFilename ) )
    {
        QFile quickFile(quickFilename);
        if (!quickFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
            return;

        QTextStream out(&quickFile);
        foreach ( TrackPoint pt, data )
            out << "TP " << pt.toTxt() << "\n";
        foreach ( TrackWaypoint pt, waypoints )
            out << "WP " << pt.toTxt() << "\n";
    }
    // Otherwise, assume we must only add the last point.
    else if ( data.size() > 0 )
    {
        QFile quickFile(quickFilename);
        if (!quickFile.open(QIODevice::Append | QIODevice::Text))
            return;

        QTextStream out(&quickFile);
        if ( tp != NULL )
            out << "TP " << tp->toTxt() << "\n";
        if ( wp != NULL )
            out << "WP " << wp->toTxt() << "\n";
    }
    setModified( false, extra_modified );
}


bool LayerTrack::isLiveTrack()
{
    return is_live_track;
}
